/*
 * Copyright (C) Michael Martin & Antonio Carrozzini (TBJD)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.tbjd.client.managers;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.tbjd.client.beans.Task;
import org.tbjd.client.constants.NotificationsConstants;
import org.tbjd.client.constants.TimeConstants;
import org.tbjd.client.exceptions.TaskManagerException;
import org.tbjd.client.factories.TaskFactory;
import org.tbjd.client.mediators.ChiefMediator;
import org.tbjd.client.states.StateBeginFrozenASAP;
import org.tbjd.client.states.StateBeginFrozenSPEC;
import org.tbjd.client.states.StateEndFrozenASAP;
import org.tbjd.client.states.StateEndFrozenSPEC;
import org.tbjd.client.utils.TimeUtils;

public class TaskManager {

	private List<Task> lstBeans = null;
	long offset = 0;

	/**
	 * <p>
	 * According to the changed value of the received {@link org.tbjd.client.beans.Task task}, compute the other related values
	 * </p>
	 * 
	 * @param map
	 *            contains the changed value (key: DATEMANAGER_ACTION_[property]), the associated bean (key: DATEMANAGER_BEAN) and the sender of the
	 *            action (key: DATEMANAGER_SENDER)
	 * @return List<Task> all changed beans
	 */
	public List<Task> manageDates(Map<Integer, Object> map) throws TaskManagerException {

		lstBeans = new ArrayList<Task>();

		Task task = (Task) map.get(NotificationsConstants.DATEMANAGER_BEAN);

		Date originalBeginDate = task.getBeginDate();
		Date originalEndDate = task.getEndDate();

		if (map.containsKey(NotificationsConstants.DATEMANAGER_ACTION_beginDate)) {
			//String newBeginDateToString = (String) map.get(NotificationsConstants.DATEMANAGER_ACTION_beginDate);
			//Date newBeginDate = (Date) DateTimeFormat.getShortDateFormat().parse(newBeginDateToString);
			Date newBeginDate = (Date) map.get(NotificationsConstants.DATEMANAGER_ACTION_beginDate);

			// if task is a phase
			if (task.getType() == Task.PHASE) {
				offset = TimeUtils.getOffset(originalBeginDate, newBeginDate);

				// shift it
				shiftPhase(task, offset);

			} else {

				// change appropriate property
				task.getState().beginDateChangedTemplate(task, newBeginDate);

				// update successors and phase's dates and duration
				updateSuccessorsAndPhase(task, originalEndDate);

			}

		} else if (map.containsKey(NotificationsConstants.DATEMANAGER_ACTION_endDate)) {
			Date newEndDate = (Date) map.get(NotificationsConstants.DATEMANAGER_ACTION_endDate);

			// if task is a phase
			if (task.getLstChildren() != null && task.getLstChildren().size() > 0) {
				offset = TimeUtils.getOffset(originalEndDate, newEndDate);

				// shift it
				shiftPhase(task, offset);

			} else {

				// change appropriate property
				task.getState().endDateChangedTemplate(task, newEndDate);

				// update successors and phase's dates and duration
				updateSuccessorsAndPhase(task, originalEndDate);

			}

		} else if (map.containsKey(NotificationsConstants.DATEMANAGER_ACTION_duration)) {
			Float newDuration = (Float) map.get(NotificationsConstants.DATEMANAGER_ACTION_duration);

			// change appropriate property
			task.getState().durationChangedTemplate(task, newDuration);

			// update successors and phase's dates and duration
			updateSuccessorsAndPhase(task, originalEndDate);

		} else if (map.containsKey(NotificationsConstants.DATEMANAGER_ACTION_progress)) {
			int newProgress = (Integer) map.get(NotificationsConstants.DATEMANAGER_ACTION_progress);

			// change appropriate property
			task.getState().progressChangedTemplate(task, newProgress);

			// update successors and phase's dates and duration
			updateSuccessorsAndPhase(task, originalEndDate);

		} else if (map.containsKey(NotificationsConstants.DATEMANAGER_ACTION_effort)) {
			Float newEffort = (Float) map.get(NotificationsConstants.DATEMANAGER_ACTION_effort);

			// change appropriate property
			task.getState().effortChangedTemplate(task, newEffort);

			// update successors and phase's dates and duration
			updateSuccessorsAndPhase(task, originalEndDate);

		} else if (map.containsKey(NotificationsConstants.DATEMANAGER_ACTION_resource)) {

			// change appropriate property
			task.getState().resourceChangedTemplate(task);

			// update successors and phase's dates and duration
			updateSuccessorsAndPhase(task, originalEndDate);

		}

		return lstBeans;
	}

	/**
	 * @param task
	 * @param originalEndDate
	 */
	private void updateSuccessorsAndPhase(Task task, Date originalEndDate) {
		lstBeans.add(task);

		offset = TimeUtils.getOffset(originalEndDate, task.getEndDate());

		// shift every successor
		for (Task successor : task.getSuccessors()) {
			if (successor.getState().isASAPChainingMode()) {
				shiftTask(successor, offset);
			}
		}

		// update phase dates and duration
		if (task.getParent() != null) {
			updatePhase(task.getParent().getLstChildren(), task.getParent());
		}
	}

	/**
	 * <p>
	 * Updates every child of the given task (parent) with the given offset
	 * </p>
	 * <p>
	 * Changes the State of the child if it has at least one external predecessor in ASAP mode
	 * </p>
	 * 
	 * @param phase
	 *            the task containing child to be updated
	 * @param offset
	 *            the amount of time in milliseconds to offset children
	 */
	protected void shiftPhase(Task phase, long offset) {
		// shift phase
		phase.getState().shift(phase, offset);
		lstBeans.add(phase);

		// for each child of the phase
		for (Task eachChild : phase.getLstChildren()) {

			// if the child has predecessors
			if (eachChild.getPredecessors() != null) {

				// for each predecessor
				for (Task eachPredecessor : eachChild.getPredecessors()) {

					// if predecessor is not in the same phase AND is in ASAP ChainingMode
					if (((eachPredecessor.getParent() != null && !eachPredecessor.getParent().equals(eachChild.getParent())) || eachPredecessor
							.getParent() == null)
							&& eachPredecessor.getState().isASAPChainingMode()) {

						// set ChainingMode to Manual
						if (eachChild.getState() instanceof StateBeginFrozenASAP) {
							eachChild.setState(new StateBeginFrozenSPEC(eachChild.getState().getDurationModeState()));
							eachChild.setChainingModeASAP(false);

						} else if (eachChild.getState() instanceof StateEndFrozenASAP) {
							eachChild.setState(new StateEndFrozenSPEC(eachChild.getState().getDurationModeState()));
							eachChild.setChainingModeASAP(false);

						}

						// stop the loop because one positive case is enough
						break;
					}
				}
			}

			// if not already updated
			if (!lstBeans.contains(eachChild)) {

				// shift it with its successors
				shiftTask(eachChild, offset);
			}

		}

	}

	/**
	 * Recursive method that updates every successor for the given task with the given offset
	 * 
	 * @param taskToUpdate
	 *            the reference task for which successors will be updated
	 * @param offset
	 *            the amount of time to offset successors (in hours)
	 */
	private void shiftTask(Task taskToUpdate, long offset) {

		if (!lstBeans.contains(taskToUpdate) && offset != 0) {

			taskToUpdate.getState().shift(taskToUpdate, offset);
			lstBeans.add(taskToUpdate);

			for (Task successor : taskToUpdate.getSuccessors()) {
				shiftTask(successor, offset);
			}
		}

		return;

	}

	/**
	 * <p>
	 * Update begin date, end date, duration, total of progress, total of left days
	 * </p>
	 */
	public void updatePhase(List<Task> lstChildren, Task phase) {
		System.out.println("TaskManager.updatePhase()");
		// set younger and older child
		Task youngerChild = lstChildren.get(0);
		Task olderChild = lstChildren.get(0);
		int averageProgress = 0;

		for (Task eachChild : lstChildren) {
			if (olderChild.getEndDate().compareTo(eachChild.getEndDate()) <= 0) {
				olderChild = eachChild;
			} else {
				youngerChild = eachChild;
			}

			averageProgress += eachChild.getProgress();
		}

		// set begin and end dates
		phase.setBeginDate(youngerChild.getBeginDate());
		phase.setEndDate(olderChild.getEndDate());

		// set duration
		phase.setDuration((olderChild.getEndDate().getTime() - youngerChild.getBeginDate().getTime()) / TimeConstants.HOUR_IN_MILLIS);

		// set progress
		if (phase.getLstChildren() != null && phase.getLstChildren().size() > 0) {
			averageProgress = averageProgress / phase.getLstChildren().size();
			phase.setProgress(averageProgress);
		}

		// if lstBeans isn't null, it means we're calling this method while updating children of the phase
		lstBeans.add(phase);
	}

	/**
	 * <p>
	 * According to the changed value of the received {@link org.tbjd.client.beans.Task task}, compute the other related values
	 * </p>
	 * 
	 * @param map
	 *            contains the changed value (key: TASKMANAGER_ACTION_[property]), the associated bean (key: TASKMANAGER_BEAN) and the sender of the
	 *            action (key: TASKMANAGER_SENDER)
	 * @return List<Task> all changed beans
	 */
	public List<Task> managePositions(Map<Integer, Object> map) throws TaskManagerException {

		lstBeans = new ArrayList<Task>();

		if (((Map<?, ?>) map).containsKey(NotificationsConstants.TASKMANAGER_ACTION_add)) {
			

			Task selectedTask = ((Task) ((Map<?, ?>) map).get(NotificationsConstants.TASK_ADDED));
			int level = ((Integer) ((Map<?, ?>) map).get(NotificationsConstants.TASK_LEVEL));

			Task newTask = TaskFactory.getAnEmptyTask();

			newTask.setLevel(level+1);

			if (selectedTask != null) {
				if (selectedTask.getParent() != null) {
					newTask.setParent(selectedTask.getParent());
				}

				if (selectedTask.getType() == Task.PHASE) {
					newTask.setParent(selectedTask);
					newTask.setLevel(level + selectedTask.getLstChildren().size());
				}
			}

			if (newTask.getLevel() < 0) {
				throw new TaskManagerException("The level must be greater or equal to zero !");
			}

			List<Task> lstTasks = ((ChiefMediator) ChiefMediator.getInstance()).getTasks();

			// add the new task in the list
			lstTasks.add(newTask.getLevel(), newTask);

			// and update every task level starting from the new one
			for (int i = newTask.getLevel(); i < lstTasks.size(); i++) {
				lstTasks.get(i).setLevel(i);
				lstBeans.add(lstTasks.get(i));
			}

			if (newTask.getParent() != null && newTask.getParent().getType() == Task.PHASE) {
				updatePhase(newTask.getParent().getLstChildren(), newTask.getParent());
			}

			return lstBeans;

		} else if (((Map<?, ?>) map).containsKey(NotificationsConstants.TASKMANAGER_ACTION_delete)) {
			Task selectedTask = ((Task) ((Map<?, ?>) map).get(NotificationsConstants.TASKMANAGER_ACTION_delete));

			if (selectedTask == null) {
				throw new TaskManagerException("A task must be selected !");
			}

			if (selectedTask.getType() == Task.PHASE) {
				System.out.println("TaskManager.managePositions() TASKMANAGER_ACTION_delete phase " + selectedTask.getLstChildren().size());

				List<Task> lstTasks = ((ChiefMediator) ChiefMediator.getInstance()).getTasks();

				// Delete the children of the phase in the list of tasks
				for (Task children : selectedTask.getLstChildren()) {
					lstTasks.remove(children);
				}

				// Delete phase
				lstTasks.remove(selectedTask);

				// and update every task level starting from the deleted one and below
				for (int i = selectedTask.getLevel(); i < lstTasks.size(); i++) {
					lstTasks.get(i).setLevel(i);
					lstBeans.add(lstTasks.get(i));
				}

				return lstBeans;

			} else {
				System.out.println("TaskManager.managePositions() TASKMANAGER_ACTION_delete task" + selectedTask.toString());

				List<Task> lstTasks = ((ChiefMediator) ChiefMediator.getInstance()).getTasks();

				// Delete the children of the phase in the list of tasks
				lstTasks.remove(selectedTask);

				// and update every task level starting from the deleted one and below
				for (int i = selectedTask.getLevel(); i < lstTasks.size(); i++) {
					lstTasks.get(i).setLevel(i);
					lstBeans.add(lstTasks.get(i));
				}

				return lstBeans;
			}
		} else if (((Map<?, ?>) map).containsKey(NotificationsConstants.TASKMANAGER_ACTION_typeSwitched)) {
			Task selectedTask = ((Task) ((Map<?, ?>) map).get(NotificationsConstants.TASKMANAGER_ACTION_typeSwitched));

			if (selectedTask == null) {
				throw new TaskManagerException("A task must be selected !");
			}

			lstBeans.add(selectedTask);

			// if phase, phase switch to task, reset the parent of each child and remove the children from phase
			if (selectedTask.getType() == Task.PHASE) {
				selectedTask.setType(Task.TASK);

				for (Task child : selectedTask.getLstChildren()) {
					child.setParent(null);
					lstBeans.add(child);
				}

				selectedTask.getLstChildren().clear();

			} else if (selectedTask.getType() == Task.TASK) {
				selectedTask.setType(Task.PHASE);

			} else {

			}

			return lstBeans;

		}else if (((Map<?, ?>) map).containsKey(NotificationsConstants.TASKMANAGER_ACTION_treeGridItemMoved_origin)) {
			
			lstBeans = new ArrayList<Task>();
			
			List<Task> lstTasks = ((ChiefMediator) ChiefMediator.getInstance()).getTasks();
			
			Task taskOrigin = (Task) ((Map<?, ?>) map).get(NotificationsConstants.TASKMANAGER_ACTION_treeGridItemMoved_origin);
			Task taskTarget = (Task) ((Map<?, ?>) map).get(NotificationsConstants.TASKMANAGER_ACTION_treeGridItemMoved_target);

			System.out.println("TreeGrid.updateItemsAndBeans() origin bean=" + taskOrigin.getName());
			System.out.println("TreeGrid.updateItemsAndBeans() target bean=" + taskTarget.getName());

			
			if(taskOrigin.getType() == Task.PHASE){
				lstTasks.remove(taskOrigin);
				lstTasks.add(taskTarget.getLevel(), taskOrigin);
				
				for (int i = 0; i < taskOrigin.getLstChildren().size(); i++) {
					lstTasks.remove(taskOrigin.getLstChildren().get(i));
					lstTasks.add(taskTarget.getLevel() + i + 1, taskOrigin.getLstChildren().get(i));
				}
				
			}else if (taskOrigin.getParent() == null) {

				//taskTarget is a phase
				if (taskTarget.getType() == Task.PHASE) {
					System.out.println("task dragged into a phase : (done)");
					taskTarget.getLstChildren().add(taskOrigin);
					taskOrigin.setParent(taskTarget);
					lstTasks.remove(taskOrigin);
					lstTasks.add(taskTarget.getLevel(), taskOrigin);
					
					updatePhase(taskTarget.getLstChildren(), taskTarget);
				//taskTarget is a task
				}else if(taskTarget.getParent() == null){
					lstTasks.remove(taskOrigin);
					lstTasks.add(taskTarget.getLevel(), taskOrigin);
					System.out.println("task dragged into a task : (done)");
				}else if (taskTarget.getParent() != null) {
					System.out.println("task dragged into a subtask : (done)");
					
					taskTarget.getParent().getLstChildren().add(taskOrigin);
					taskOrigin.setParent(taskTarget.getParent());
					lstTasks.remove(taskOrigin);
					lstTasks.add(taskTarget.getLevel(), taskOrigin);
					
					updatePhase(taskTarget.getLstChildren(), taskTarget);
				}
			
			//taskOrigin is a subTask
			} else if(taskOrigin.getParent() != null) {
				
				//taskTarget is a phase
				if (taskTarget.getType() == Task.PHASE) {
					System.out.println("subtask dragged into a phase : (done)");
					taskOrigin.getParent().getLstChildren().remove(taskOrigin);
					taskTarget.getLstChildren().add(taskOrigin);
				
					lstTasks.remove(taskOrigin);
					lstTasks.add(taskTarget.getLevel(), taskOrigin);
					
					updatePhase(taskTarget.getLstChildren(), taskTarget);
					updatePhase(taskOrigin.getParent().getLstChildren(), taskOrigin);

					taskOrigin.setParent(taskTarget);
				//taskTarget is a task
				} else if (taskTarget.getParent() == null) {
					System.out.println("subtask dragged into a task : (done)");
					taskOrigin.getParent().getLstChildren().remove(taskOrigin);
					lstTasks.remove(taskOrigin);
					lstTasks.add(taskTarget.getLevel(), taskOrigin);
					
					updatePhase(taskOrigin.getParent().getLstChildren(), taskOrigin.getParent());

					taskOrigin.setParent(null);
				//taskTarget is a subTask
				} else {
					System.out.println("subtask dragged into a subtask : (done)");
					taskOrigin.getParent().getLstChildren().remove(taskOrigin);
					taskTarget.getParent().getLstChildren().add(taskOrigin);
					
					lstTasks.remove(taskOrigin);
					lstTasks.add(taskTarget.getLevel(), taskOrigin);
					
					System.out.println("subtask dragged " + taskOrigin.getName());
					
					updatePhase(taskTarget.getParent().getLstChildren(), taskTarget.getParent());
					updatePhase(taskOrigin.getParent().getLstChildren(), taskOrigin.getParent());

					taskOrigin.setParent(taskTarget.getParent());
				}
			}
			
			for (int i = taskOrigin.getLevel(); i < taskTarget.getLevel(); i++) {
				lstTasks.get(i).setLevel(i);
				lstBeans.add(lstTasks.get(i));
			}
			
			return lstBeans;
		}

		throw new TaskManagerException("Invalid key in taskManager !");
	}

}
